#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <fstream>
#include <boost/filesystem/operations.hpp>
#include "boost/filesystem.hpp"

using namespace std;

#include <QtGui>

MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // Disable / hide certain components
    ui->progressBar->setVisible(false);
    ui->progressBar->setMinimum(0);
    ui->progressBar->setMaximum(100);
    ui->stopButton->setEnabled(false);
    ui->stopButton->setVisible(false); // it doesn't do anything yet, so stop it from showing
    ui->removeButton->setEnabled(false);

    // "uintmax_t" isn't a type recognized for passing between threads by QT...
    // Long explanation: http://doc.trolltech.com/4.2/qt.html#ConnectionType-enum
    qRegisterMetaType<uintmax_t>("uintmax_t");
    QObject::connect(&findFileSizes, SIGNAL(sendFileSizes(qint64, uintmax_t)), this, SLOT(receiveFileSizes(qint64, uintmax_t)));

    QObject::connect(&backup, SIGNAL(doneBackingUp(unsigned int, unsigned int)), this, SLOT(backupComplete(unsigned int, unsigned int)));
    QObject::connect(&backup, SIGNAL(copiedBytes(qint64)), this, SLOT(copiedBytes(qint64)));

    QObject::connect(&findFileSizes, SIGNAL(sendFileSizePercentage(int)), this, SLOT(fileSizeProgress(int)));


    // Open backup.txt which contains the list of items to be backed up
    ifstream backupTxt("backup.txt", ios::in);

    // If they are trying to back up something with an excessively long name,
    // well, too bad for now.
    const int length = 257;
    char line[length];
    bool isFile;

    // initializes sum for all of the files and directories to 0
    sumFileSizes = 0;

    if (backupTxt.is_open() && backupTxt.good() )
    {
        // The destination path is always the first thing in the file
        backupTxt.getline( line, length, '\n');
        backupPath = line;
        if ( !(backupPath.endsWith("/")) && !(backupPath == ""))
        backupPath.append("/");
        ui->backupLine->setText(backupPath);


        // Just keep going until the end of the file, populating the list of items
        // with strings from the file
        while(true) {
            if ( backupTxt.eof() ) break;

            backupTxt.getline( line, length, '\n' );

            if ( line[strlen(line)-1] == '/' )
                isFile = false;
            else
                isFile = true;

            addStringToList(line, isFile);
        }

        backupTxt.close();
    }

    // Even though we just loaded from the file, we still want to save right back to the file.
    // This is in case backup.txt contained files/directories that didn't exist. We would still call
    // addStringToList() on them, but they wouldn't actually be added.
    backupListToFile();

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_browseButton_clicked()
{
    // Pop up a dialog
    QString dir = QFileDialog::getExistingDirectory(
            this, tr("Choose back-up destination"),
            "/home",
            QFileDialog::ShowDirsOnly
            | QFileDialog::DontResolveSymlinks);

    // Make sure it actually returned a directory
    if ( dir != "" ) {

        if ( !(dir.endsWith("/")))
        dir.append("/");

        // sets new destination path and adds it to the settings file
        ui->backupLine->setText(dir);
        backupPath = ui->backupLine->text();
        addToConsole("Changed directory to " + dir);
        backupListToFile();
    }

}

void MainWindow::addToConsole(const QString &text) {
    ui->textEdit->append(text);
}

void MainWindow::on_removeButton_clicked()
{
    QList<QListWidgetItem*> removeList = ui->backupList->selectedItems();

    for ( QList<QListWidgetItem*>::iterator i = removeList.begin(); i != removeList.end(); ++i ) {
        delete ui->backupList->takeItem(ui->backupList->row(*i));
    }

    if ( ui->backupList->count() == 0 ) {
        ui->removeButton->setEnabled(false);
    }

    backupListToFile();

    // I don't know if we'll even want this in the program, but if you click 'Remove' and nothing
    // is selected, it will select the first item.
    /*
    if ( removeList.empty() && ui->backupList->count > 0 ) {
        ui->backupList->setCurrentItem(ui->backupList->item(0));
    }
    */
}

void MainWindow::backupListToFile() {

    // Right now, I am using ios::trunc. A better way would be to keep track of what
    // this file contains in RAM and just make changes to the file when necessary, but
    // for now, this function will just overwrite everything in the file with everything in
    // the list of things to be backed up.
    ofstream backupTxt("backup.txt", ios::out | ios::trunc);

    if (backupTxt.is_open() && backupTxt.good())
    {
        // Writes the destination path as the first thing in the settings file
        backupTxt << backupPath.toStdString() << endl;

        for ( int i = 0; i < ui->backupList->count(); i++ ) {
            backupTxt << ui->backupList->item(i)->text().toStdString() << endl;
        }

        backupTxt.close();
    } else {
        addToConsole("Error opening \"backup.txt\"");
    }

}

void MainWindow::addStringToList(const QString &text, bool isFile) {

    // Make sure we're not adding blank things to the list
    if ( text == "" ) return;

    // Make sure there are no '\' characters in the text (replace them with '/')
    QString fixedText = text;
    fixedText.replace(QChar('\\'), QChar('/'));

    // Set up the file/directory-specific stuff here.
    //
    // Directories are always added with slashes at the end to denote that they're directories,
    // and files are added to the list with blue text
    QColor color(0,0,0);
    if ( isFile ) {
        color.setBlue(255);
        QFile checkExist(fixedText);
        if ( !checkExist.exists() ) {
            return;
        }
    } else {
        if ( !fixedText.endsWith("/") ) fixedText.append("/");

        QDir checkExist(fixedText);
        if ( !checkExist.exists() ) {
            return;
        }
    }

    // Now check to see if the file is already on the list. There are two cases for this:
    // 1. The text to be added directly matches text of an item on the list
    // 2. The text to be added is a substring of a directory on the list
    //
    // Also, if we're adding a directory, check to see if any other items in the list are contained in it
    // (their row values will be added to removeRows to be removed afterward so that the list doesn't get
    // screwed up while iterating over it)
    QList<int> removeRows;

    for ( int i = 0; i < ui->backupList->count(); i++ ) {
        QString listItemText = ui->backupList->item(i)->text();

        // Check for exact match
        if ( listItemText == fixedText ) return;

        // Check to see if this is a directory and we should be deleting children
        if ( !isFile && listItemText.startsWith(fixedText) ) {
            removeRows.append(i);
        }

        // Directories end in '/'. If this isn't true, continue
        if ( !listItemText.endsWith('/')  ) continue;

        // If we got to this point, then "fixedText" is a directory, so check for containment
        if ( fixedText.startsWith(listItemText) ) {
            addToConsole("Not adding " + fixedText + " because it is contained in a directory already on the list.");
            return;
        }
    }

    // We need to start at the end of the list of rows and remove backwards.
    // To see an easy example of why, suppose we had two items in the list and we wanted to remove them both.
    // If we did this with a forward iterator, we would remove row 0, then row 1. However, after removing
    // row 0, row 1 would become row 0.
    QListIterator<int> it(removeRows);
    it.toBack();

    while (it.hasPrevious()) {
        delete ui->backupList->takeItem(it.previous());
    }


    // By this point, we know we can add to the list, so we do so
    QListWidgetItem *newItem = new QListWidgetItem();
    newItem->setText(fixedText);
    newItem->setTextColor(color);
    ui->backupList->addItem(newItem);

    ui->removeButton->setEnabled(true);
}

void MainWindow::on_addFolderButton_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(
            this, tr("Choose directory to be backed up"),
            "/home",
            QFileDialog::ShowDirsOnly
            | QFileDialog::DontResolveSymlinks);

    addStringToList(dir, false);
    backupListToFile();
}

void MainWindow::on_addFilesButton_clicked()
{
    QFileDialog dialog;
    dialog.setFileMode(QFileDialog::ExistingFiles);

    if ( dialog.exec() == QDialog::Accepted ) {
        QStringList files = dialog.selectedFiles();
        for ( int i = 0; i < files.length(); i++ ) {
            addStringToList(files.at(i), true);
        }
    }

    backupListToFile();
}

QDateTime MainWindow::fileLastModified(QString myFile)
{
    QFileInfo file(myFile);

    return file.lastModified();
}

bool MainWindow::isDirectory(QString path)
{
    if( path.endsWith("/") )
        return true;

    QDir dir(path);
    return dir.exists();
}


QString MainWindow::onlyFileName(QString file)
{
    QFileInfo file_name(file);
    return file_name.fileName();

}

void MainWindow::on_backupLine_editingFinished()
{
    // Every time you modify the destination, it will make sure it ends in a '/'
    backupPath = ui->backupLine->text();

    if( !backupPath.endsWith("/") && backupPath != "" )
        backupPath.append("/");

    ui->backupLine->setText(backupPath);
}

QStringList MainWindow::getBackupListItems() {

    QStringList backupListItems;

    for ( int i = 0; i < ui->backupList->count(); i++ ) {
        backupListItems.append(ui->backupList->item(i)->text());
    }

    return backupListItems;
}

void MainWindow::on_backupButton_clicked()
{
    // I use a boolean here so that all appropriate error messages can print before returning
    bool errored = false;

    if ( ui->backupList->count() == 0 ) {
        addToConsole("Error: nothing to back up!");
        errored = true;
    }

    if ( ui->backupLine->text() == "" ) {
        addToConsole("Error: no destination selected!");
        errored = true;
    }

    QDir dirExists(ui->backupLine->text());
    if ( !dirExists.exists() ) {
        addToConsole("Error: destination doesn't exist!");
        return;
    }


    for ( int i = 0; i < ui->backupList->count(); i++ ) {
        QString listItemText = ui->backupList->item(i)->text();

        if ( ui->backupLine->text().startsWith(listItemText) ) {
            addToConsole("MAJOR ERROR: Your backup directory is contained in a directory you're trying to back up!");
            errored = true;
        }
    }

    if ( errored ) return;

    setButtonsEnabled(false);
    // Now, we need to initialize a thread and send it off to find the size to be backed
    // up and the free space on the backup drive.
    //
    // Backup code resumes in "receiveFileSizes"
    findFileSizes.initializeVariables(getBackupListItems(), backupPath);

    ui->progressBar->setVisible(true);
    ui->progressBar->setValue(0);

    findFileSizes.start();

}


QString MainWindow::bytesToString(qint64 bytes) {
    double kb = 1024.0;
    double mb = kb * kb;
    double gb = mb * kb;
    double tb = gb * kb;

    if ( bytes > tb ) return QString::number(bytes/tb, 'f', 2) + " TB";
    if ( bytes > gb ) return QString::number(bytes/gb, 'f', 2) + " GB";
    if ( bytes > mb ) return QString::number(bytes/mb, 'f', 2) + " MB";
    if ( bytes > kb ) return QString::number(bytes/kb, 'f', 2) + " KB";
    return QString::number(bytes) + " bytes";
}

void MainWindow::receiveFileSizes(qint64 sum, uintmax_t freeSpace) {
    sumFileSizes = sum;

    addToConsole("Finished finding file sizes.");
    addToConsole(bytesToString(sum) + " to be backed up.");

    if ( sumFileSizes > (qint64)freeSpace) {
        QString backupDrive = backupPath.left(3);
        addToConsole( backupDrive + " does not have enough free space.");
        addToConsole( "Size of all of your files: " + bytesToString(sum));
        addToConsole( "Free space on " + backupDrive + " is " + bytesToString(freeSpace));
        setButtonsEnabled(true);
        return;
    }

    if ( sumFileSizes == 0 ) {
        addToConsole("Nothing needs to be backed up.\n");
        setButtonsEnabled(true);
        ui->progressBar->setVisible(false);
        return;
    }

    addToConsole("Backup has started!");

    // Start the actual backup process now (resume at "backupComplete")
    ui->progressBar->setVisible(true);
    ui->progressBar->setValue(0);
    //ui->stopButton->setEnabled(true);
    bytesCopied = 0;

    backup.initializeVariables(getBackupListItems(), backupPath);
    backup.start();
}

void MainWindow::backupComplete(unsigned int numFoldersCopied, unsigned int numFilesCopied) {
    // This may seem picky, but we don't want to say "backed up 1 folders"
    QString correctFolderWord("folders");
    QString correctFileWord("files");
    if ( numFoldersCopied == 1 ) correctFolderWord = "folder";
    if ( numFilesCopied == 1 ) correctFileWord = "file";

    // Print statistics
    addToConsole("\nDone!\nCopied " + QString::number(numFoldersCopied) + " " +
                 correctFolderWord + " and " + QString::number(numFilesCopied) + " " + correctFileWord + ".");
    addToConsole("Backed up " + bytesToString(sumFileSizes) + ".\n");


    setButtonsEnabled(true);
    ui->progressBar->setVisible(false);
    ui->stopButton->setEnabled(false);
}

void MainWindow::setButtonsEnabled(bool enabled) {
    ui->removeButton->setEnabled(enabled);
    ui->addFilesButton->setEnabled(enabled);
    ui->addFolderButton->setEnabled(enabled);
    ui->backupButton->setEnabled(enabled);
    ui->browseButton->setEnabled(enabled);
}

void MainWindow::copiedBytes(qint64 bytes) {
    bytesCopied += bytes;
    ui->progressBar->setValue((int)(bytesCopied * 100.0 / sumFileSizes));
}

void MainWindow::fileSizeProgress(int percent) {
    ui->progressBar->setValue(percent);
}

void MainWindow::on_stopButton_clicked()
{
    addToConsole("This doesn't actually do anything yet!");
}
